package org.example.aop;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.example.infrastructure.persistent.redis.IRedisService;
import org.example.types.annotations.DCCValue;
import org.example.types.annotations.RateLimiterAccessInterceptor;
import org.example.types.common.Constants;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

@Slf4j
@Aspect
@Component
public class RateLimiterAOP {

    @DCCValue("rateLimiterSwitch:open")
    private String rateLimiterSwitch;

    @Resource
    private IRedisService redisService;

    private final Cache<String, RateLimiter> loginRecord = CacheBuilder.newBuilder()
            .expireAfterWrite(1, TimeUnit.MINUTES)
            .build();

    @Pointcut("@annotation(org.example.types.annotations.RateLimiterAccessInterceptor)")
    public void aopPoint(){}

    @Around("aopPoint() && @annotation(rateLimiterAccessInterceptor)")
    public Object doRouter(ProceedingJoinPoint jp, RateLimiterAccessInterceptor rateLimiterAccessInterceptor) throws Throwable {

        //限流开关【open 打开 close 关闭】
        if(rateLimiterSwitch.isEmpty() || rateLimiterSwitch.equals("close")){
            return jp.proceed();
        }

        String key = rateLimiterAccessInterceptor.key();
        if(key.isEmpty()){
            throw new RuntimeException("uId is null");
        }

        String attrValue = getAttrValue(key, jp.getArgs());
        log.info("aop attr {}",attrValue);

        String blacklistCacheKey = Constants.RedisKey.ACTIVITY_RATELIMITER_BLACKLIST_COUNT + attrValue;

        if(!attrValue.equals("all") && rateLimiterAccessInterceptor.blacklistCount() != 0 && redisService.isExists(blacklistCacheKey) && redisService.getAtomicLong(blacklistCacheKey)> rateLimiterAccessInterceptor.blacklistCount()){
            log.info("限流-超频次拦截：{}", attrValue);
            return fallbackMethodResult(jp, rateLimiterAccessInterceptor.fallbackMethod());
        }

        RateLimiter rateLimiter = loginRecord.getIfPresent(attrValue);
        if(rateLimiter == null){
            rateLimiter = RateLimiter.create(rateLimiterAccessInterceptor.permitsPerSecond());
            loginRecord.put(attrValue,rateLimiter);
        }
        if(!rateLimiter.tryAcquire()){
            if (rateLimiterAccessInterceptor.blacklistCount() != 0){
                if(!redisService.isExists(blacklistCacheKey)){
                    redisService.setAtomicLong(blacklistCacheKey,1);
                    redisService.getRAtomicLong(blacklistCacheKey).expire(24, TimeUnit.HOURS);
                }else {
                    redisService.incr(blacklistCacheKey);
                }
            }
            log.info("限流-超频次拦截：{}", attrValue);
            return fallbackMethodResult(jp, rateLimiterAccessInterceptor.fallbackMethod());
        }
        return jp.proceed();
    }



    /**
     * 调用用户配置的回调方法，当拦截后，返回回调结果。
     */
    private Object fallbackMethodResult(JoinPoint jp, String fallbackMethod) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Signature sig = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) sig;
        Method method = jp.getTarget().getClass().getMethod(fallbackMethod, methodSignature.getParameterTypes());
        return method.invoke(jp.getThis(), jp.getArgs());
    }

    /**
     * 实际根据自身业务调整，主要是为了获取通过某个值做拦截
     */
    public String getAttrValue(String attr, Object[] args) {
        if (args[0] instanceof String) {
            return args[0].toString();
        }
        String filedValue = null;
        for (Object arg : args) {
            try {
                if (StringUtils.isNotBlank(filedValue)) {
                    break;
                }
                // filedValue = BeanUtils.getProperty(arg, attr);
                // fix: 使用lombok时，uId这种字段的get方法与idea生成的get方法不同，会导致获取不到属性值，改成反射获取解决
                filedValue = String.valueOf(this.getValueByName(arg, attr));
            } catch (Exception e) {
                log.error("获取路由属性值失败 attr：{}", attr, e);
            }
        }
        return filedValue;
    }

    /**
     * 获取对象的特定属性值
     *
     * @param item 对象
     * @param name 属性名
     * @return 属性值
     * @author tang
     */
    private Object getValueByName(Object item, String name) {
        try {
            Field field = getFieldByName(item, name);
            if (field == null) {
                return null;
            }
            field.setAccessible(true);
            Object o = field.get(item);
            field.setAccessible(false);
            return o;
        } catch (IllegalAccessException e) {
            return null;
        }
    }

    /**
     * 根据名称获取方法，该方法同时兼顾继承类获取父类的属性
     *
     * @param item 对象
     * @param name 属性名
     * @return 该属性对应方法
     * @author tang
     */
    private Field getFieldByName(Object item, String name) {
        try {
            Field field;
            try {
                field = item.getClass().getDeclaredField(name);
            } catch (NoSuchFieldException e) {
                field = item.getClass().getSuperclass().getDeclaredField(name);
            }
            return field;
        } catch (NoSuchFieldException e) {
            return null;
        }
    }

}
